package ggroubig

import (
	"context"
	"errors"
	"fmt"
	"github.com/nats-io/nats.go"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"wzgames/internal/codec/sfs"
	"wzgames/internal/constant"
	"wzgames/internal/gdao/roudao"
	"wzgames/internal/gloader"
	"wzgames/internal/model/gamep"
	"wzgames/internal/model/roulettep"
	"wzgames/internal/response"
	"wzgames/internal/services/games"
	"wzgames/pkg/actor/actor"
	"wzgames/pkg/actor/actorservice"
	"wzgames/pkg/logger"
	"wzgames/pkg/remote/etcd"
	"wzgames/pkg/utils/dateutil"
)

// 处理routower的播报，状态同步等，和小飞机类似

type PlateMon struct {
	mu  sync.RWMutex
	pp  *GRouletteBig
	pid *actor.PID
	// 轮次信息和状态
	roundDir string
	stateDir string
	evtch    chan *etcd.KvEvt
	stopCh   chan struct{}

	state       RouState // 当前状态
	cfg         *roulettep.RouletteConfig
	notifiers   []*Notifier // 播报分组
	notifierIdx int32

	roundInfo  *roulettep.RouRound // 当前轮次信息
	elapseChan chan CountDown
	currElapse int64
	stopElapse chan struct{}
	rouDao     roudao.RouletteDao

	roundCounts map[int64]*Counter // 结算用
	keyElapse   string
	elapseSub   *nats.Subscription // 监听倒计时播报
	keyBetsRet  string
	betsSub     *nats.Subscription // 监听投注播报
}

func newPlate(pp *GRouletteBig) *PlateMon {
	ac := &PlateMon{
		pp:          pp,
		evtch:       make(chan *etcd.KvEvt, 1),
		stopCh:      make(chan struct{}, 1),
		elapseChan:  make(chan CountDown, 256),
		stopElapse:  make(chan struct{}, 1),
		state:       RouState{State: constant.RouUnActive},
		keyBetsRet:  constant.RouBetRetNKeySub + "." + pp.InstIdStr(),
		roundCounts: make(map[int64]*Counter),
	}

	name := fmt.Sprintf("roulette-%s", strconv.FormatInt(ac.pp.GameInstId(), 10))
	props := actor.PropsFromProducer(func() actor.Actor { return ac })
	pid, err := actorservice.ActorSystem().Root.SpawnNamed(props, name)
	if err != nil {
		logger.Error("[RouletteBig] spawn roulette plate actor error", zap.Int64("InstId", ac.pp.GameInstId()), zap.Error(err))
		return nil
	}
	ac.pid = pid
	ac.rouDao = roudao.RouletteDao{Driver: gloader.CommMgr().GetDBProxy(constant.MysqlOfRoulette).Ds()}

	return ac
}

func (ac *PlateMon) Pid() *actor.PID {
	return ac.pid
}

func (ac *PlateMon) Init(cfg *roulettep.RouletteConfig) error {
	ac.cfg = cfg
	ac.roundDir = fmt.Sprintf("%s/round-%v", constant.CcRoulette, ac.pp.GameInstId())
	ac.stateDir = fmt.Sprintf("%s/state", ac.roundDir)
	err := ac.loadRound() // etcd加载当前轮次信息和状态
	if err != nil {
		return err
	}
	go ac.onData() // 监听ETCD状态
	// 初始化播报分组
	for i := int32(0); i < cfg.Notifiers; i++ {
		ac.notifiers = append(ac.notifiers, newNotifier(cfg.NotifyChanSize))
	}
	go ac.onCountDown() // 接收播报通知

	betsSub, err1 := gloader.CommMgr().NatsC().Subscribe(ac.keyBetsRet, ac.doBetsRet)
	if err1 != nil {
		logger.Error("[RouletteBig] start bets notify sub fail on subscribe", zap.String("RoundDir", ac.roundDir), zap.String("SubKey", ac.keyBetsRet), zap.Error(err1))
		return err1
	}
	ac.betsSub = betsSub

	elapseSub, err2 := gloader.CommMgr().NatsC().Subscribe(constant.RouCountDownNKeySub+"."+ac.pp.InstIdStr(), ac.doCountDown)
	if err2 != nil {
		logger.Error("[RouletteBig] start roulette countdown sub fail on subscribe", zap.String("RoundDir", ac.roundDir), zap.String("SubKey", constant.RouCountDownNKeySub), zap.Error(err2))
		return err2
	}
	ac.elapseSub = elapseSub

	return ac.watchRound()
}

func (ac *PlateMon) Update(cfg *roulettep.RouletteConfig) {
	ac.mu.Lock()
	defer ac.mu.Unlock()
	if cfg.Notifiers > ac.cfg.Notifiers {
		diff := cfg.Notifiers - ac.cfg.Notifiers
		for i := int32(0); i < diff; i++ {
			ac.notifiers = append(ac.notifiers, newNotifier(cfg.NotifyChanSize))
		}
	}
	ac.cfg = cfg
}

func (ac *PlateMon) loadRound() error {
	err := gloader.CClient().GetRawValues(ac.roundDir, func(key string, data []byte) error {
		if key == ac.roundDir {
			return ac.parseRound(data)
		} else if key == ac.stateDir {
			return ac.parseState(data)
		}
		return nil
	})
	if err != nil {
		logger.Error("[RouletteBig] load roulette round info fail", zap.String("RoundDir", ac.roundDir), zap.Error(err))
		return err
	}
	return nil
}

func (ac *PlateMon) watchRound() error {
	err := gloader.CClient().Watch(context.Background(), ac.roundDir, true, ac.evtch)
	if err != nil {
		logger.Error("[RouletteBig] watch roulette round fail", zap.String("RoundDir", ac.roundDir), zap.Error(err))
		return err
	}

	return nil
}

// parseRound 轮次数据
func (ac *PlateMon) parseRound(data []byte) error {
	if data == nil {
		return errors.New("[RouletteBig] invalid round data")
	}
	rd := &roulettep.RouRound{}
	err := proto.Unmarshal(data, rd)
	if err != nil {
		logger.Error("[RouletteBig] parse roulette round fail on Unmarshal", zap.String("RoundDir", ac.roundDir), zap.Error(err))
		return err
	}
	ac.mu.Lock()
	ac.roundInfo = rd
	_, ok := ac.roundCounts[rd.RoundId]
	if !ok {
		// 启动对应轮次的Counter
		ac.roundCounts[rd.RoundId] = NewCounter(rd.RoundId, ac)
	}
	ac.mu.Unlock()
	return nil
}

// parseState 状态数据
func (ac *PlateMon) parseState(data []byte) error {
	if data == nil {
		return errors.New("[RouletteBig] invalid state data")
	}
	nState := &roulettep.RouState{}
	err := proto.Unmarshal(data, nState)
	if err != nil {
		logger.Error("[RouletteBig] parse roulette state fail on Unmarshal", zap.String("RoundDir", ac.roundDir), zap.Error(err))
		return err
	}
	newState := RouState{
		RoundId:    nState.RoundId,
		State:      nState.NewStateId,
		StateStart: nState.StateStart, // 状态开始时间
		End:        nState.RoundEnd,
		RetNum:     nState.RetNum,        // 开奖号码
		RetColor:   nState.RetColor,      // 号码对应的颜色
		RetGrp:     nState.RetGrp,        // 号码对应的组别
		Dateline:   dateutil.UnixMilli(), // 记录时间
	}
	ac.mu.Lock()
	ac.state = newState
	actorservice.Send(ac.pid, newState) // 播报状态
	ac.mu.Unlock()
	if newState.State == constant.RouOpenState {
		// 开奖后结算
		actorservice.Send(ac.pid, RoundCounterSettle{RoundId: newState.RoundId, RetNum: newState.RetNum, RetColor: newState.RetColor, RetGrp: nState.RetGrp})
	}
	return nil
}

func (ac *PlateMon) RoundInfo() (int64, int32) {
	ac.mu.RLock()
	defer ac.mu.RUnlock()
	return ac.state.RoundId, ac.state.State
}

func (ac *PlateMon) CurrentElapse() int64 {
	return ac.currElapse
}

// CanBet 是否可以下注
func (ac *PlateMon) CanBet() (bool, int64) {
	ac.mu.RLock()
	if ac.roundInfo == nil {
		ac.mu.RUnlock()
		return false, 0
	}
	state := ac.state.State
	start := ac.state.StateStart
	roundId := ac.roundInfo.RoundId
	ac.mu.RUnlock()
	t := dateutil.UnixMilli()
	// 投注状态且在规定的时间内
	if state == constant.RouBetState && t <= timeOut26+start {
		return true, roundId
	}
	return false, 0
}

// updateRouNodeRound 新轮盘在该游戏节点上的状态日志
func (ac *PlateMon) updateRouNodeRound(roundId int64, retNum, retColor, retGrp string) {
	ent := &roulettep.RouNodeRoundEntity{
		NodeId:   gloader.BootConfig().Node().NodeId,
		InstId:   ac.pp.GameInstId(),
		RoundId:  roundId,
		Status:   constant.RouOpenState,
		Result:   strings.Join([]string{retNum, retColor, retGrp}, constant.Comma),
		Dateline: dateutil.UnixMilli(),
	}
	_ = ac.rouDao.CreateRouNodeRound(ent)
}

// onData 轮盘ETCD状态数据变更
func (ac *PlateMon) onData() {
	for {
		select {
		case e := <-ac.evtch:
			switch e.Type {
			case etcd.KvEventNew, etcd.KvEventUpdate:
				if e.Key == ac.roundDir {
					_ = ac.parseRound(e.Value)
				} else if e.Key == ac.stateDir {
					_ = ac.parseState(e.Value)
				}
			case etcd.KvEventDelete:

			}
		case <-ac.stopCh:
			return
		}
	}
}

// doCountDown nats消息处理
func (ac *PlateMon) doCountDown(msg *nats.Msg) {
	c := response.AcquireElapse()
	defer response.ReleaseElapse(c)
	err := proto.Unmarshal(msg.Data, c)
	if err != nil {
		logger.Error("[RouletteBig] do countdown fail on proto.Unmarshal", zap.String("RoundDir", ac.roundDir), zap.Error(err))
		return
	}
	ac.currElapse = c.Remain
	ac.elapseChan <- CountDown{Remain: c.Remain, State: c.State}
}

// onCountDown 播报倒计时
func (ac *PlateMon) onCountDown() {
	for {
		select {
		case m := <-ac.elapseChan:
			ac.broadcastElapse(m.Remain, m.State)
		case <-ac.stopElapse:
			return
		}
	}
}

func (ac *PlateMon) doBetsRet(msg *nats.Msg) {
	ntf := &gamep.BetsNotify{}
	err := proto.Unmarshal(msg.Data, ntf)
	if err != nil {
		logger.Error("[RouletteBig] do bets notify fail on proto.Unmarshal", zap.String("RoundDir", ac.roundDir), zap.Error(err))
		return
	}
	actorservice.Send(ac.pid, ntf)
}

func (ac *PlateMon) NextRoundSeedH256() string {
	if ac.roundInfo != nil && ac.roundInfo.Next != nil {
		return ac.roundInfo.Next.GetH256()
	}
	return constant.EmptyStr
}

func (ac *PlateMon) ServerSeed() string {
	if ac.roundInfo != nil {
		return ac.roundInfo.Seed
	}
	return constant.EmptyStr
}

func (ac *PlateMon) Receive(ctx actor.Context) {
	switch m := ctx.Message().(type) {
	case RouState: // 播报状态
		ac.broadcastState(m)
	case *gamep.BetsNotify: // 播报投注消息
		ac.broadcastBetsNotify(m)
	case *games.BetDelay:
		ac.rouBetInfo(m)
	case RoundCounterSettle: // 轮次结算
		ac.RoundCounterSettle(m)
	case RoundCounterRemove:
		ac.removeRoundCounter(m.RoundId)
	}
}

func (ac *PlateMon) removeRoundCounter(roundId int64) {
	defer logger.Recover()
	ac.mu.RLock()
	ct, ok := ac.roundCounts[roundId]
	ac.mu.RUnlock()
	if ok {
		ct.Clear()
		ac.mu.Lock()
		delete(ac.roundCounts, roundId)
		ac.mu.Unlock()
	}
}

func (ac *PlateMon) RoundCounterSettle(data RoundCounterSettle) {
	defer logger.Recover()
	ac.mu.RLock()
	ct, ok := ac.roundCounts[data.RoundId]
	ac.mu.RUnlock()
	if ok {
		go ct.Settle(ac.pp.GetGameId(), data)
	}
	ac.updateRouNodeRound(data.RoundId, data.RetNum, data.RetColor, data.RetGrp)
}

func (ac *PlateMon) rouBetInfo(data *games.BetDelay) {
	defer logger.Recover()
	if data.BetObj == nil {
		return
	}
	ac.mu.RLock()
	ct, ok := ac.roundCounts[data.BetObj.RoundId]
	ac.mu.RUnlock()
	if ok {
		ct.BetInfo(data)
	}
}

func (ac *PlateMon) Register(tb *games.GameTable) {
	defer logger.Recover()
	newIdx := atomic.AddInt32(&ac.notifierIdx, 1)
	if newIdx >= ac.cfg.Notifiers {
		newIdx = 0
		atomic.StoreInt32(&ac.notifierIdx, 0)
	}
	notifier := ac.notifiers[newIdx]
	notifier.Register(tb)
	tb.QueIdx = newIdx
}

func (ac *PlateMon) UnRegister(tb *games.GameTable) {
	notifier := ac.notifiers[tb.QueIdx]
	if notifier != nil {
		notifier.Unregister(tb.Player.ID())
	}
}

func (ac *PlateMon) broadcastState(state RouState) {
	defer logger.Recover()

	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.KeyChangeState)
	var itemC uint16 = 3
	if state.State == constant.RouOpenState {
		itemC = 6
	}
	xw.WriteSfsObjectPre(sfs.ParamId, itemC)
	xw.WriteIntS(constant.KeyNewStateId, state.State)
	xw.WriteIntS(sfs.KeyCode, 200)
	xw.WriteLongS(constant.KeyRoundId, state.RoundId)

	if state.State == constant.RouOpenState {
		xw.WriteStringS(constant.KeyNum, state.RetNum)
		xw.WriteStringS(constant.KeyColor, state.RetColor)
		xw.WriteStringS(constant.KeyGrp, state.RetGrp)
	}

	for _, nt := range ac.notifiers {
		m := response.AcquireMsgO13()
		xw.WriteTo(m)
		nt.Notify(m)
	}
}

func (ac *PlateMon) broadcastElapse(remain int64, state int32) {
	xw := sfs.XWriter{}
	defer xw.Reset()
	var paramN uint16 = 3
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.KeyCountDown)
	xw.WriteSfsObjectPre(sfs.ParamId, paramN)
	xw.WriteIntS(sfs.KeyCode, 200)
	xw.WriteLongS(constant.KeyRemain, remain)
	xw.WriteIntS(constant.KeyState, state)

	for _, nt := range ac.notifiers {
		m := response.AcquireMsgO13()
		xw.WriteTo(m)
		nt.Notify(m)
	}
}

func (ac *PlateMon) broadcastBetsNotify(data *gamep.BetsNotify) {
	defer logger.Recover()

	for _, nt := range ac.notifiers {
		m := response.AcquireMsgO13()
		m.Write(data.Data)
		nt.Notify(m)
	}
}

func (ac *PlateMon) Destroy() {
	ac.stopCh <- struct{}{}
	ac.stopElapse <- struct{}{}
	for _, nt := range ac.notifiers {
		nt.Close()
	}
	if ac.elapseSub != nil {
		ac.elapseSub.Unsubscribe()
		ac.elapseSub.Drain()
	}
	if ac.betsSub != nil {
		ac.betsSub.Unsubscribe()
		ac.betsSub.Drain()
	}
	gloader.CClient().UnWatch(ac.stateDir)
	actorservice.Stop(ac.pid)
}
